package com.sun.mongodb.core.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import com.sun.mongodb.core.entity.BaseMongo;
import com.sun.mongodb.core.query.AggregationWrapper;
import com.sun.mongodb.core.query.CriteriaWrapper;
import com.sun.mongodb.core.params.LookUpParam;
import com.sun.mongodb.core.reflection.ReflectionUtil;
import com.sun.mongodb.core.reflection.SerializableFunction;
import com.sun.mongodb.core.service.BaseMongoService;
import com.sun.mongodb.core.update.UpdateBuilder;
import com.sun.mongodb.core.util.PageUtils;
import com.sun.mongodb.util.ReflectionKit;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.BulkOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.*;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.util.Pair;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 *  T尸体类中:   @Document(collection = "my_sworders")  表名
 *       @Id   主键   ，@Field  字段
 */
public class BaseMongoServiceImpl<T extends BaseMongo> implements BaseMongoService<T> {
    protected Class<T> entityClass = this.currentModelClass();
    @Autowired
    protected MongoTemplate mongoTemplate;

    public T save(T entity) {
        entity.setCreateTime(new Date());
        ReflectionUtil.setDefaultInitVaule(entity);
        return mongoTemplate.insert(entity);
    }

    public Collection<T> saveBatch(Collection<T> var1) {
        for(T t:var1){
            t.setCreateTime(new Date());
            ReflectionUtil.setDefaultInitVaule(t);
        }
        return mongoTemplate.insertAll(var1);
    }

    public void removeById(Serializable id) {
        Query query = Query.query(Criteria.where("_id").is(id) );
        DeleteResult result= mongoTemplate.remove(query,entityClass);
    }

    @Override
    public boolean remove(CriteriaWrapper queryWrapper) {
        mongoTemplate.remove(queryWrapper.buildQuery(),entityClass);
        return true;
    }


    public void removeByIds(Collection<? extends Serializable> idList) {
        Query query = Query.query(Criteria.where("_id").in(idList) );
        mongoTemplate.remove(query,entityClass);
    }

    public boolean updateById(T entity) {
        Query query = Query.query(Criteria.where("_id").is(entity.getId()));
        UpdateBuilder update=getTUpdate(entity,false);
        mongoTemplate.updateFirst(query,update,entityClass);
        return true;
    }

    public boolean updateBatchById(Collection<T> entityList) {
        BulkOperations operations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, entityClass);
        List<Pair<Query, Update>> list = new ArrayList<Pair<Query, Update>>();
        for (T t : entityList) {
            Query query = new Query(Criteria.where("_id").is(t.getId()));
            UpdateBuilder update =getTUpdate(t,false);
            Pair<Query, Update> pair = Pair.of(query, update);
            list.add(pair);
        }
        operations.updateMulti(list);
        operations.execute();
        return true;
    }
    /**
     * 修改 所有满足条件的
     * @param entity
     * @param updateWrapper
     * @return
     */
    @Override
    public boolean update(T entity, CriteriaWrapper updateWrapper) {
        Query query = updateWrapper.buildQuery();
        UpdateResult updateResult=  mongoTemplate.updateMulti(query,getTUpdate(entity,false),entityClass);
        return true;
    }
    /**
     * 修改 第一条 满足条件的
     * @param entity
     * @param updateWrapper
     * @return
     */
    @Override
    public boolean updateSingle(T entity, CriteriaWrapper updateWrapper) {
        Query query = updateWrapper.buildQuery();
        UpdateResult updateResult= mongoTemplate.updateFirst(query,getTUpdate(entity,false),entityClass);
        return true;
    }

    @Override
    public boolean update(Update update, CriteriaWrapper updateWrapper) {
        UpdateResult updateResult= mongoTemplate.updateMulti(updateWrapper.buildQuery(),update,entityClass);
        return false;
    }

    @Override
    public boolean updateSingle(Update update, CriteriaWrapper updateWrapper) {
        UpdateResult updateResult= mongoTemplate.updateFirst(updateWrapper.buildQuery(),update,entityClass);

        return false;
    }

    public boolean saveOrUpdate(T var1) {
        if(StringUtils.isBlank(var1.getId())){
            mongoTemplate.insert(var1);
        }else{
            this.updateById(var1);
        }
        return true;
    }

    public T getById(Serializable id) {
        return  mongoTemplate.findById(id,entityClass);
    }

    public List<T> listByIds(Collection<? extends Serializable> idList) {
        return mongoTemplate.find(Query.query(Criteria.where("_id").in(idList)),entityClass);
    }

    public List<T> list() {
        return mongoTemplate.findAll(entityClass);
    }

    @Override
    public PageUtils<T> page(PageUtils<?> page) {
        PageUtils<T> respPage = new PageUtils<T>();
        respPage.setSize(page.getSize());
        respPage.setPage(page.getPage());
        // 查询出总条数
        if (page.getQueryCount()) {
            Long count = count();
            respPage.setTotal(count);
        }
        Query query = new Query();
        query.skip((page.getPage() - 1) * page.getSize());// 从那条记录开始
        query.limit(page.getSize());// 取多少条记录
        respPage.setList(mongoTemplate.find(query,entityClass));
        return respPage;
    }

    @Override
    public PageUtils<T> page(PageUtils<?> page, CriteriaWrapper queryWrapper) {
        PageUtils<T> respPage = new  PageUtils<T>();
        respPage.setSize(page.getSize());
        respPage.setPage(page.getPage());
        // 查询出总条数
        if (page.getQueryCount()) {
            Long count = count(queryWrapper);
            respPage.setTotal(count);
        }
        Query query = queryWrapper.buildQuery();
        long pageSkip = (page.getPage() - 1) * page.getSize();
        if(queryWrapper.getSkip()!=null&&queryWrapper.getSkip()>0){
            pageSkip += queryWrapper.getSkip();
        }
        query.skip(pageSkip);// 从那条记录开始
        query.limit(page.getSize());// 取多少条记录
        respPage.setList(mongoTemplate.find(query,entityClass));
        return respPage;

    }

    @Override
    public T findAndRemove(CriteriaWrapper updateWrapper) {
        return mongoTemplate.findAndRemove(updateWrapper.buildQuery(),entityClass);
    }

    @Override
    public T findAndReplace(CriteriaWrapper updateWrapper,T t) {
        return mongoTemplate.findAndReplace(updateWrapper.buildQuery(),t);
    }

    @Override
    public List<T> aggregate(Aggregation agg) {
        AggregationResults results= mongoTemplate.aggregate(agg,entityClass,entityClass);
        return results.getMappedResults();
    }
    @Override
    public <V> List<V> aggregate(Aggregation agg,Class<V> clazz){
        AggregationResults results= mongoTemplate.aggregate(agg,entityClass,clazz);
        return results.getMappedResults();
    }

    @Override
    public List<T> aggregate(AggregationWrapper wrapper) {
        return this.aggregate(wrapper.buildAggregation());
    }

    /**
     * 表关联查询 聚合
     * @param joinClazz    关联表类
     * @param localColumn  主表-关联字段
     * @param foreignColumn  关联表-关联字段
     * @param as  别名
     * @return
     */
    @Override
    public <E, R> AggregationOperation lookup(Class<?> joinClazz, SerializableFunction<E, R> localColumn, SerializableFunction<E, R> foreignColumn, String as) {
        return Aggregation.lookup(mongoTemplate.getCollectionName(joinClazz), ReflectionUtil.getFieldName(localColumn), ReflectionUtil.getFieldName(foreignColumn),as);
    }


    /**
     * 聚合管道函数  $graphLookup  图查询   对集合执行递归搜索。
     * @param joinClazz    collection 表名
     * @param startWith
     * @param connectFrom  主表字段-字段名
     * @param connectTo     关联字段名
     * @param as     别名
     * @param maxDepth   递归深度
     * @param depthField  深度字段
     * @param criteria  搜索范围查询条件
     * @return
     */
    @Override
    public <E, R> AggregationOperation graphLookup(Class<?> joinClazz, List<Object> startWith, SerializableFunction<E, R> connectFrom, SerializableFunction<E, R> connectTo, String as, Long maxDepth, SerializableFunction<E, R> depthField, CriteriaWrapper criteria) {
        AggregationOperation aggregationOperation = new AggregationOperation() {
            public Document toDocument(AggregationOperationContext aggregationOperationContext) {
                DBObject graphLookup = new BasicDBObject("from", mongoTemplate.getCollectionName(entityClass))
                        .append("startWith", "$" +  ReflectionUtil.getFieldName(connectFrom))
                        .append("connectFromField", ReflectionUtil.getFieldName(connectFrom))
                        .append("connectToField", ReflectionUtil.getFieldName(connectTo))
                        .append("as", as)
                        .append("maxDepth", maxDepth)
                        .append("depthField", ReflectionUtil.getFieldName(depthField))
                        .append("restrictSearchWithMatch", criteria.buildCriteria());
                return new Document("$graphLookup", graphLookup);
            }
        };
        return  aggregationOperation;
    }

    public T getOne(CriteriaWrapper queryWrapper){
        List<T> list =mongoTemplate.find(queryWrapper.buildQuery().limit(1),entityClass);
        return list==null?null:list.get(0);
    }

    @Override
    public long count() {
        return mongoTemplate.getCollection(mongoTemplate.getCollectionName(entityClass)).estimatedDocumentCount();
    }

    @Override
    public long count(CriteriaWrapper queryWrapper) {
        return mongoTemplate.count(queryWrapper.buildQuery() ,entityClass);
    }

    @Override
    public List<T> list(CriteriaWrapper queryWrapper) {
        Query query =queryWrapper.buildQuery();
        return mongoTemplate.find(query,entityClass);
    }


    protected Class<T> currentModelClass() {
        return (Class<T>)ReflectionKit.getSuperClassGenericType(this.getClass(), 0);
    }




    protected static UpdateBuilder getTUpdate(Object t,Boolean isSetNull){
        // 获取类中的全部定义字段
        Field[] fields = t.getClass().getDeclaredFields();
        UpdateBuilder update  = null;
        for(Field field:fields ){
            // 排除 临时字段
            Transient tst = field.getAnnotation(Transient.class);
            if(tst!=null){
                continue;
            }
            // 假设不为空。设置可见性，然后返回
            field.setAccessible( true );
            try{
                Object fieldVal = field.get(t);
                if(isSetNull){
                    if(update==null){
                        update = new UpdateBuilder();
                        if(t instanceof BaseMongo){
                            update.currentDate(BaseMongo::getUpdateTime);
                        }
                    }
                    org.springframework.data.mongodb.core.mapping.Field mongoField = field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
                    if(mongoField!=null){
                        update.set(mongoField.value(),fieldVal);
                    }else{
                        String key = StrUtil.toUnderlineCase(field.getName());
                        update.set(key,fieldVal);
                    }
                }else{
                    if(fieldVal!=null){
                        if(update==null){
                            update = new UpdateBuilder();
                            if(t instanceof BaseMongo){
                                update.currentDate(BaseMongo::getUpdateTime);
                            }
                        }
                        org.springframework.data.mongodb.core.mapping.Field mongoField = field.getAnnotation(org.springframework.data.mongodb.core.mapping.Field.class);
                        if(mongoField!=null){
                            update.set(mongoField.value(),fieldVal);
                        }else{
                            String key = StrUtil.toUnderlineCase(field.getName());
                            update.set(key,fieldVal);
                        }
                    }
                }
            }catch (IllegalArgumentException ex){

            }catch (IllegalAccessException ex){

            }
        }
        return update;
    }
    @Override
    public String getEntityCollectionName(Class<?> joinClazz){
        return mongoTemplate.getCollectionName(joinClazz);
    }

    @Override
    public List<T> leftJoinList(CriteriaWrapper queryWrapper, List<LookUpParam> lookUpParamList) {
        AggregationWrapper wrapper = new AggregationWrapper();
        wrapper.match(queryWrapper);
        if(CollectionUtil.isNotEmpty(lookUpParamList)){
            for(LookUpParam param:lookUpParamList){
                wrapper.lookup(param);
            }
        }
        return this.aggregate(wrapper);
    }


}
